//  Copyright 2011 Charlie Garrett
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
//  Value.h
//

#ifndef VALUE_H
#define VALUE_H

#include <cmath>
#include <sstream>

// How should we represent scalar values that we can operate on with builtin functions,
// such as ints, strings, real numbers?  We could make them separate subclasses of Value
// or instantiations of a templated structure.  Or, we could make them a structure containing
// a union, as below, so that different types are distinguished dynamically.  When we generate
// the put/get/set/unify instructions, we know the type of value at hand, which would enable 
// us to use different types statically.  But the types of heap cells are not known statically.
// They require extra type info either in the Value itself, or in the Cell data structure or
// on the side.

// Note that heap contents don't have to be fixed size.  In fact, there might be benefits
// to packing the most common heap cells into single words and using two or more words for
// rarer or larger values.  However, if we intend to point directly to heap cells, we need
// to maintain an alignment of pointer width.

// Represent scalar values that logic programs can manipulate.

// TODO: Add arbitrary precision integers, rational numbers and complex numbers.

namespace logic {
    using namespace std;
    
    struct Value {
        enum Kind {
            BOOLEAN = 0,
            INTEGER = 1,
            FLOAT = 2,
            STRING = 3
        };
        
        Kind kind;
        union {
            bool boolean;
            long integer;
            double real;
            string *str;
        } data;
        
        Value(bool b) {
            kind = BOOLEAN;
            data.boolean = b;
        }
        
        Value(long i) {
            kind = INTEGER;
            data.integer = i;
        }
        
        Value(double d) {
            kind = FLOAT;
            data.real = d;
        }
        
        Value(const string &s) {
            kind = STRING;
            data.str = new string(s);
        }
        
        ~Value() {
            if (kind == STRING && data.str != NULL) {
                delete data.str;
            }
        }
        
        Value operator==(const Value &other) const {
            if (kind != other.kind) {
                return Value(false);
            }
            switch (kind) {
                case BOOLEAN:
                    return Value(data.boolean == other.data.boolean);
                case INTEGER:
                    return Value(data.integer == other.data.integer);
                case FLOAT:
                    return Value(data.real == other.data.real);
                case STRING:
                    return Value((*data.str) == (*other.data.str));
            }
        }
        
        Value operator<(const Value &other) const {
            assert(kind == other.kind);
            switch (kind) {
                case INTEGER:
                    return Value(data.integer < other.data.integer);
                case FLOAT:
                    return Value(data.real < other.data.real);
                case STRING:
                    return Value((*data.str) < (*other.data.str));
                default:
                    assert(false);
            }
        }

        Value operator<=(const Value &other) const {
            assert(kind == other.kind);
            switch (kind) {
                case INTEGER:
                    return Value(data.integer <= other.data.integer);
                case FLOAT:
                    return Value(data.real <= other.data.real);
                case STRING:
                    return Value((*data.str) <= (*other.data.str));
                default:
                    assert(false);
            }
        }
        
        Value operator>(const Value &other) const {
            assert(kind == other.kind);
            switch (kind) {
                case INTEGER:
                    return Value(data.integer > other.data.integer);
                case FLOAT:
                    return Value(data.real > other.data.real);
                case STRING:
                    return Value((*data.str) > (*other.data.str));
                default:
                    assert(false);
            }
        }
        
        Value operator>=(const Value &other) const {
            assert(kind == other.kind);
            switch (kind) {
                case INTEGER:
                    return Value(data.integer >= other.data.integer);
                case FLOAT:
                    return Value(data.real >= other.data.real);
                case STRING:
                    return Value((*data.str) >= (*other.data.str));
                default:
                    assert(false);
            }
        }
        
        Value operator!=(const Value &other) const {
            Value result = (*this) == other;
            result.data.boolean = !result.data.boolean;
            return result;
        }
        
        Value operator+(const Value &other) const {
            assert(kind == other.kind);
            switch (kind) {
                case INTEGER:
                    return Value(data.integer + other.data.integer);
                case FLOAT:
                    return Value(data.real + other.data.real);
                default:
                    assert(false);
            }
        }
        
        Value operator-(const Value &other) const {
            assert(kind == other.kind);
            switch (kind) {
                case INTEGER:
                    return Value(data.integer * other.data.integer);
                case FLOAT:
                    return Value(data.real * other.data.real);
                default:
                    assert(false);
            }
        }
        
        Value operator*(const Value &other) const {
            assert(kind == other.kind);
            switch (kind) {
                case INTEGER:
                    return Value(data.integer * other.data.integer);
                case FLOAT:
                    return Value(data.real * other.data.real);
                default:
                    assert(false);
            }
        }

        Value operator/(const Value &other) const {
            assert(kind == other.kind);
            switch (kind) {
                case INTEGER:
                    return Value(data.integer / other.data.integer);
                case FLOAT:
                    return Value(data.real / other.data.real);
                default:
                    assert(false);
            }
        }

        Value operator%(const Value &other) const {
            assert(kind == other.kind);
            switch (kind) {
                case INTEGER:
                    return Value(data.integer % other.data.integer);
                default:
                    assert(false);
            }
        }
        
        Value abs() const {
            switch (kind) {
                case INTEGER:
                    return Value(data.integer >= 0 ? data.integer : -data.integer);
                case FLOAT:
                    return Value(fabs(data.real));
                default:
                    assert(false);
            }
        }
        
        Value sign() const {
            switch (kind) {
                case INTEGER:
                    return Value(data.integer >= 0 ? 1L : -1L);
                case FLOAT:
                    // Result is an INTEGER
                    return Value(data.real >= 0.0 ? 1L : -1L);
                default:
                    assert(false);
            }
        }
        
        Value min(const Value &other) const {
            switch (kind) {
                case INTEGER:
                    return Value(std::min(data.integer, other.data.integer));
                case FLOAT:
                    return Value(std::min(data.real, other.data.real));
                default:
                    assert(false);
            }
        }
        
        Value max(const Value &other) const {
            switch (kind) {
                case INTEGER:
                    return Value(std::max(data.integer, other.data.integer));
                case FLOAT:
                    return Value(std::max(data.real, other.data.real));
                default:
                    assert(false);
            }
        }
        
        Value sin() const {
            switch (kind) {
                case INTEGER:
                    // Result is a FLOAT
                    return Value(std::sin(data.integer));
                case FLOAT:
                    return Value(std::sin(data.real));
                default:
                    assert(false);
            }
        }
        
        Value cos() const {
            switch (kind) {
                case INTEGER:
                    // Result is a FLOAT
                    return Value(std::cos(data.integer));
                case FLOAT:
                    return Value(std::cos(data.real));
                default:
                    assert(false);
            }
        }
        
        Value tan() const {
            switch (kind) {
                case INTEGER:
                    // Result is a FLOAT
                    return Value(std::tan(data.integer));
                case FLOAT:
                    return Value(std::tan(data.real));
                default:
                    assert(false);
            }
        }
        
        Value asin() const {
            switch (kind) {
                case INTEGER:
                    // Result is a FLOAT
                    return Value(std::asin(data.integer));
                case FLOAT:
                    return Value(std::asin(data.real));
                default:
                    assert(false);
            }
        }
        
        Value acos() const {
            switch (kind) {
                case INTEGER:
                    // Result is a FLOAT
                    return Value(std::acos(data.integer));
                case FLOAT:
                    return Value(std::acos(data.real));
                default:
                    assert(false);
            }
        }
        
        Value atan() const {
            switch (kind) {
                case INTEGER:
                    // Result is a FLOAT
                    return Value(std::atan(data.integer));
                case FLOAT:
                    return Value(std::atan(data.real));
                default:
                    assert(false);
            }
        }
        
        Value atan2(const Value &other) const {
            switch (kind) {
                case INTEGER:
                    // Result is a FLOAT
                    return Value(std::atan2(data.integer, other.data.integer));
                case FLOAT:
                    return Value(std::atan2(data.real, other.data.real));
                default:
                    assert(false);
            }
        }
        
        Value sqrt() const {
            switch (kind) {
                case INTEGER:
                    // Result is a FLOAT
                    return Value(std::sqrt(data.integer));
                case FLOAT:
                    return Value(std::sqrt(data.real));
                default:
                    assert(false);
            }
        }
        
        Value log() const {
            switch (kind) {
                case INTEGER:
                    // Result is a FLOAT
                    return Value(std::log(data.integer));
                case FLOAT:
                    return Value(std::log(data.real));
                default:
                    assert(false);
            }
        }
        
        Value log10() const {
            switch (kind) {
                case INTEGER:
                    // Result is a FLOAT
                    return Value(std::log10(data.integer));
                case FLOAT:
                    return Value(std::log10(data.real));
                default:
                    assert(false);
            }
        }
        
        Value exp() const {
            switch (kind) {
                case INTEGER:
                    // Result is a FLOAT
                    return Value(std::exp(data.integer));
                case FLOAT:
                    return Value(std::exp(data.real));
                default:
                    assert(false);
            }
        }
        

        

        string ToString() const {
            ostringstream os;
            switch (kind) {
                case BOOLEAN:
                    os << data.boolean;
                    break;
                case INTEGER:
                    os << data.integer;
                    break;
                case FLOAT:
                    os << data.real;
                    break;
                case STRING:
                    os << *data.str;
                    break;
            }
            return os.str();
        }
    };
}  // namespace logic

#endif  // VALUE_H
    
